﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using DevelopersToolbox;
using Microsoft.CSharp;
using System.Text.RegularExpressions;

namespace CodeTools {
    public partial class CSharpShellControl : UserControl, IExecutableTool {
        private const string DEFAULT_CODE = @"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

public class Program {
    public static void Main(string[] args) {
        Console.WriteLine(""Hello"");
    }
}
";

        public CSharpShellControl() {
            InitializeComponent();
            _code.Text = DEFAULT_CODE;
        }

        private void _execute_Click(object sender, EventArgs e) {
            Execute();
        }

        public void Execute() {
            ConsoleClear();

            CompilerResults results = Compile(_code.Text);
            if (results == null) {
                return;
            }
            MethodInfo entryMethod = GetEntryPointMethod(results);
            if (entryMethod == null) {
                return;
            }
            RunMethod(entryMethod);
        }

        private void RunMethod(MethodInfo method) {
            TextWriter prevOut = Console.Out;
            TextWriter prevError = Console.Error;
            TextReader prevIn = Console.In;
            try {
                TextWriter newOut = new MyConsoleTextWriter(_results);
                TextWriter newError = new MyConsoleTextWriter(_results);
                Console.SetOut(newOut);
                Console.SetError(newError);

                string[] args = new string[] { };
                method.Invoke(null, new object[] { args });
            } catch (Exception ex) {
                ConsoleWriteLine("Error running code:");
                ConsoleWriteLine("   {0}", ex);
            } finally {
                Console.SetOut(prevOut);
                Console.SetError(prevError);
                Console.SetIn(prevIn);
            }
        }

        private MethodInfo GetEntryPointMethod(CompilerResults results) {
            Type entryType = results.CompiledAssembly.GetType("Program");
            if (entryType == null) {
                ConsoleWriteLine("Could not find entry point type: Program");
                return null;
            }
            MethodInfo entryMethod = entryType.GetMethod("Main", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string[]) }, null);
            if (entryMethod == null) {
                ConsoleWriteLine("Could not find entry point method: Program.Main(string[])");
                return null;
            }
            return entryMethod;
        }

        private CompilerResults Compile(string code) {
            try {
                var providerOptions = new Dictionary<string, string> {
                    {"CompilerVersion", "v3.5"}
                };
                CodeDomProvider provider = new CSharpCodeProvider(providerOptions);

                CompilerParameters options = new CompilerParameters {
                    GenerateInMemory = true,
                    IncludeDebugInformation = true,
                };
                options.ReferencedAssemblies.Add("System.dll");
                options.ReferencedAssemblies.Add("System.Core.dll");

                CompilerResults results = provider.CompileAssemblyFromSource(options, code);
                if (results.Errors.Count > 0) {
                    ConsoleWriteLine("Errors compiling code");
                    foreach (CompilerError error in results.Errors) {
                        ConsoleWriteLine("   {0}", error);
                    }
                } else {
                    return results;
                }
            } catch (Exception ex) {
                ConsoleWriteLine("Errors compiling code");
                ConsoleWriteLine("   {0}", ex.ToString());
            }
            return null;
        }

        private void ConsoleClear() {
            _results.Text = "";
        }

        private void ConsoleWriteLine(string format, params object[] args) {
            string str = string.Format(format, args);
            _results.Text = string.Format("{0}{1}\n", _results.Text, str);
        }

    }
}
